/*
 * ImageBoardCtrl.cpp
 *
 *  Created on: Apr 13, 2009
 *      Author: wrice
 */
#include <fstream>
#include "PANEImp.h"
namespace PANEImp {

string PANEImageBoardElement::TagName = "BOARD";

struct PANEImageBoardElement::Impl {

	class MouseClickEventListener : public EventListener {
	public:
		virtual void handleEvent(Event * evt) {
			PANEImageBoardElement * const board = dynamic_cast< PANEImageBoardElement * >( evt->getTarget() );
			ModelReconstructor * const model = board->getModelReconstructor();
			MouseEvent * const mouseEvt = dynamic_cast<MouseEvent*>(evt);
			const Point2D point( mouseEvt->getClientX() - board->getLeftOffset(),
								 mouseEvt->getClientY() - board->getTopOffset()  );

			switch( board->getInteractionMethod() ) {

			case PANEImageBoardElement::DrawPoint:
				if( board->isThereFeaturePoint(point) ) break;

				model->addPoint2D( board->getCurrentFrameNumber(), point );
				board->requestRepaint();
				break;

			case PANEImageBoardElement::DrawLine:
				if( false == board->isThereFeaturePoint(point) )  break;
				if( board->isPointSelected() ) {
					FeaturePoint * lineFrom = board->getSelectedFeaturePoint();
					FeaturePoint * lineTo = board->selectFeaturePoint(point);
					if( lineFrom == lineTo ) break;
					if( model->hasFeatureLine( lineFrom, lineTo ) ) break;

					model->addLine3D( lineFrom, lineTo );
					board->requestRepaint();
				}
				board->selectFeaturePoint(point);
				break;

			case PANEImageBoardElement::Erase:
				if( false == board->isThereFeaturePoint(point) ) break;

				model->removeFeaturePoint( board->selectFeaturePoint( point ) );
				board->deselectPoint();
				board->requestRepaint();
				break;

			default:
				break;
			}
		}
	};

	class MouseDragEventListener : public EventListener {
	public:
		virtual void handleEvent(Event * evt) {
			PANEImageBoardElement * const board = dynamic_cast< PANEImageBoardElement * >( evt->getTarget() );

			ModelReconstructor * const model = board->getModelReconstructor();
			MouseEvent * const mouseEvt = dynamic_cast<MouseEvent*>(evt);
			const Point2D point( mouseEvt->getClientX() - board->getLeftOffset(),
								 mouseEvt->getClientY() - board->getTopOffset()  );

			switch( board->getInteractionMethod() ) {

			case PANEImageBoardElement::Move:
				if( mouseEvt->getCtrlKey() ) {
					if( board->isThereFeaturePoint(point) ) {
						board->selectFeaturePoint( point );
					}
				} else {
					board->deselectPoint();
				}

				if( board->isPointSelected() ) {
					FeaturePoint * const fpoint = board->getSelectedFeaturePoint();
					model->moveFeaturePoint( fpoint, board->getCurrentFrameNumber(), point );
				}
				board->requestRepaint();
				break;

			case PANEImageBoardElement::DrawLine:
				if( board->isPointSelected() ) {
					FeaturePoint * const lineFrom = board->getSelectedFeaturePoint();
					board->showTemporaryLine( lineFrom, point );
				}
				board->requestRepaint();
				break;

			default:
				break;
			}
		}
	};


	class MouseOutEventListener : public EventListener {
	public:
		virtual void handleEvent(Event * evt) {
			PANEImageBoardElement * const board = dynamic_cast< PANEImageBoardElement * >( evt->getTarget() );
			board->deselectPoint();
			board->requestRepaint();
		}
	};
};

void PANEImageBoardElement::deselectPoint() {
	selectedFeaturePoint_ = NULL;
	temporaryLineTo_ = NULL;
}

bool PANEImageBoardElement::isPointSelected() const {
	return selectedFeaturePoint_ != NULL;
}

FeaturePoint * PANEImageBoardElement::getSelectedFeaturePoint() const {
	return selectedFeaturePoint_;
}

FeaturePoint * PANEImageBoardElement::selectFeaturePoint(Point2D point) {
	return selectedFeaturePoint_ = getModelReconstructor()->
			findNearFeaturePoint( getCurrentFrameNumber(), point, getTolerableDistanceForPoint() );
}

bool PANEImageBoardElement::isThereFeaturePoint( Point2D point ) {
	return getModelReconstructor()->findNearFeaturePoint( getCurrentFrameNumber(), point, getTolerableDistanceForPoint() );
}


//FeatureLine * PANEImageBoardElement::getNearLineWithTolerance( Point2D point ) const {
//	const ModelReconstructor::FeatureLinesIterator iter = getModelReconstructor()->findNearFeatureLine( getCurrentFrameNumber(), point, getTolerableDistanceForLine() );
//	if(getModelReconstructor()->endFeatureLines() == iter) return NULL;
//	return *iter;
//}

PANEImageBoardElement::PANEImageBoardElement(PANEDocument * ownerDocument)
: PANEElement( TagName, ownerDocument)
, imagePrefix_("")
, imageSuffix_("")
, imageSequenceNumberBegin_(0)
, imageSequenceNumberEnd_(-1)
, frameNumber_(0)
, model_(NULL)
, interactionMethod_(DrawPoint)
, selectedFeaturePoint_(NULL)
, temporaryLineTo_(NULL)
, tolerableDistanceForPoint_(5.f)
, tolerableDistanceForLine_(5.f)
{
	deselectPoint();
	this->addEventListener(MouseEventImp::EVENT_CLICK, new Impl::MouseClickEventListener(), false);
	this->addEventListener(MouseEventImp::EVENT_MOUSE_MOVE, new Impl::MouseDragEventListener(), false);
	this->addEventListener(MouseEventImp::EVENT_MOUSE_OUT, new Impl::MouseOutEventListener(), false);
}

PANEImageBoardElement::~PANEImageBoardElement() {
	for( CvImagesModifiableIterator iter = cvImages_.begin(); iter != cvImages_.end(); ++iter )
		cvReleaseImage( &(*iter) );
}

void PANEImageBoardElement::setModelReconstructor( ModelReconstructor * model ) {
	model_ = model;
}

ModelReconstructor * PANEImageBoardElement::getModelReconstructor() {
	return model_;
}

void PANEImageBoardElement::setInteractionMethod( InteractionMethod interactionMethod ) {
	switch( interactionMethod ) {
	case DrawPoint:
		deselectPoint();
		break;
	case DrawLine:
		deselectPoint();
		break;
	case Erase:
		deselectPoint();
		break;
	case Move:
		deselectPoint();
		break;
	}

	interactionMethod_ = interactionMethod;
}
PANEImageBoardElement::InteractionMethod PANEImageBoardElement::getInteractionMethod() {
	return interactionMethod_;
}

unsigned long PANEImageBoardElement::getWidth() {
	if( hasDesiredWidth() ) return PANEElement::getWidth();
	return getActualWidth();
}
unsigned long PANEImageBoardElement::getHeight() {
	if( hasDesiredHeight() ) return PANEElement::getHeight();
	return getActualHeight();
}

unsigned long PANEImageBoardElement::getActualWidth() {
	const IplImage * const cvImg =  cvImages_[ getCurrentFrameNumber() ];
	if( NULL == cvImg ) return 0;
	return cvImg->width;
}

unsigned long PANEImageBoardElement::getActualHeight() {
	const IplImage * const cvImg =  cvImages_[ getCurrentFrameNumber() ];
	if( NULL == cvImg ) return 0;
	return cvImg->height;
}

void PANEImageBoardElement::loadImageFiles(
		string filenamePrefix, int sequenceBegin, int sequenceEnd, string filenameSuffix, int step )
{
	imagePrefix_ = filenamePrefix;
	imageSuffix_ = filenameSuffix;
	imageSequenceNumberBegin_ = sequenceBegin;
	imageSequenceNumberEnd_ = sequenceEnd;

	char buf[1024];
	for( int idx = imageSequenceNumberBegin_; idx < imageSequenceNumberEnd_ +1; idx += step ) {
		snprintf( buf, 1024, "%03d", idx );
		const string middlefix = buf;
		ifstream inFile;
		string eachFilename = imagePrefix_ + middlefix + imageSuffix_;
		inFile.open( eachFilename.c_str() );
		if( !inFile ) continue;

		IplImage * const img = cvLoadImage( eachFilename.c_str() );
		if( !img ) continue;

		cvImages_.push_back( img );
	}
}

FrameNumber PANEImageBoardElement::getCurrentFrameNumber() const {
	return frameNumber_;
}

void PANEImageBoardElement::setCurrentFrameNumber( FrameNumber desiredFrameNumber ) {
	requestRepaint();
	frameNumber_ = std::max( 0ul, std::min( desiredFrameNumber, getNumberOfFrames() - 1 ) );
}

unsigned long PANEImageBoardElement::getNumberOfFrames() const {
	return cvImages_.size();
}

float PANEImageBoardElement::getTolerableDistanceForPoint() const {
	return tolerableDistanceForPoint_;
}

float PANEImageBoardElement::getTolerableDistanceForLine() const {
	return tolerableDistanceForLine_;
}

void PANEImageBoardElement::render( IplImage * img ) {
	if( false == isRepaintRequested() ) return;
	if( false == getVisible() ) return;

	const int maxWidth = img->width;
	const int maxHeight = img->height;
	const long leftOffset = getLeftOffset();
	const long topOffset = getTopOffset();
	const IplImage * const cvImg =  cvImages_[ getCurrentFrameNumber() ];

	for( int i = 0; i < cvImg->height && i + topOffset < maxHeight; ++i ) {
		for( int j = 0; j < cvImg->width && j + leftOffset < maxWidth; ++j ) {
			const CvScalar aDot = cvGet2D( cvImg, i, j );
			cvSet2D( img, topOffset + i, leftOffset + j, aDot );
		}
	}

	const CvScalar redColor = CV_RGB( 255, 0, 0 );
	const CvScalar greenColor = CV_RGB( 0, 255, 0 );
	const CvScalar blueColor = CV_RGB( 0, 0, 255 );

	ModelReconstructor::PointsOnFrame voodooPoints = model_->getVoodooFeaturePointsOnOneFrame( getCurrentFrameNumber() );
	renderCircle( img, voodooPoints, 1, greenColor );

	ModelReconstructor::PointsOnFrame userPoints = model_->getUserAddedFeaturePointsOnOneFrame( getCurrentFrameNumber() );
	renderCircle( img, userPoints, 5, blueColor );

	ModelReconstructor::PointsOnFrame potentialPoints = model_->getPotentialPointsOnOneFrame( getCurrentFrameNumber() );
	renderCircle( img, potentialPoints, 5, redColor );

	ModelReconstructor::LinesOnFrame userLines = model_->getLinesOnOneFrame( getCurrentFrameNumber() );
	renderLine( img, userLines, blueColor );

	if( isThereTemporaryLineToRender() ) {
		Line2D temporaryLine (
				model_->getProjectionPoint( getCurrentFrameNumber(), getSelectedFeaturePoint() ),
				*temporaryLineTo_ );
		renderLine( img, temporaryLine, redColor );
	}

	repainted();
}

void PANEImageBoardElement::renderCircle( IplImage * img, ModelReconstructor::PointsOnFrame points
		, int thickness, CvScalar color )
{
	const long leftOffset = getLeftOffset();
	const long topOffset = getTopOffset();

	ModelReconstructor::PointsOnFrameIterator iter = points.begin();
	for(; iter != points.end(); ++iter ) {
		const int x = iter->getX();
		const int y = iter->getY();
		cvCircle( img, cvPoint( leftOffset + x, topOffset + y ), thickness, color, CV_FILLED );
	}
}

void PANEImageBoardElement::renderLine( IplImage * img, ModelReconstructor::LinesOnFrame lines, CvScalar color ) {
	ModelReconstructor::LinesOnFrameIterator iter = lines.begin();
	for(; iter != lines.end(); ++iter )
		renderLine( img, *iter, color );
}

void PANEImageBoardElement::renderLine( IplImage * img, Line2D line, CvScalar color ) {
	const long leftOffset = getLeftOffset();
	const long topOffset = getTopOffset();

	CvPoint lineFrom = cvPoint( leftOffset + line.getLineFrom().getX(), topOffset + line.getLineFrom().getY() );
	CvPoint lineTo = cvPoint( leftOffset + line.getLineTo().getX(), topOffset + line.getLineTo().getY() );
	cvLine( img, lineFrom, lineTo, color, 3 );
}

void PANEImageBoardElement::showTemporaryLine( FeaturePoint * lineFrom, Point2D lineTo ) {
	temporaryLineTo_ = new Point2D( lineTo );
}

bool PANEImageBoardElement::isThereTemporaryLineToRender() {
	return getInteractionMethod() == DrawLine && isPointSelected() && temporaryLineTo_ != NULL;
}



}
